package com.apes.framework.plugin.esb.processor.connector.http;

import com.alibaba.fastjson.JSONObject;
import com.apes.framework.plugin.esb.api.Request;
import com.apes.framework.plugin.esb.processor.connector.Connector;
import com.apes.framework.util.MapUtil;
import com.apes.framework.util.Tools;
import com.apes.oap.Constants;
import lombok.Data;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 功能：Http连接器类：HttpClient实现
 *
 * @author xul
 * @create 2017-12-02 15:13
 */
@Data
public class HttpClientConnector<T extends Request, R> implements Connector<T, R> {

    private String url = null;
    private String enc = Constants.UTF8;
    private static final int BUFFER_SIZE = 4096;
    private static final Integer timeout = 60 * 1000;
    private HttpClient client;
    private Map<String, String> headers = null;
    private String action = "POST";
    private String responseType;  // 解析类型
    private Boolean returnHeaders = false;

    public HttpClientConnector() {
        client = new HttpClient();
        client.setTimeout(timeout);
    }

    public HttpClientConnector(String url) {
        this();
        this.url = url;
    }

    @Override
    public void connect() {
        url = Tools.replace(url);
    }

    private HttpMethod createHttpMethod() {
        HttpMethod httpMethod;
        if ("GET".equalsIgnoreCase(action)) {
            httpMethod = new GetMethod(url);
        } else {
            httpMethod = new UTF8PostMethod(url);
        }
        if (headers != null) {
            for (Map.Entry<String, String> header : headers.entrySet()) {
                httpMethod.addRequestHeader(header.getKey(), Tools.replace(header.getValue()));
            }
        }
        return httpMethod;
    }

    @Override
    public void disconnect() {
    }

    @Override
    public R process(T data) {
        Object body = data.getData();
        Assert.notNull(body, this.getClass().getName() + ": body must not be null.");
        HttpMethod httpMethod = createHttpMethod();
        try {
            if (httpMethod instanceof GetMethod) {
                String queryString;
                if (body instanceof Map) {
                    NameValuePair[] nameValuePairs = MapToNameValuePair((Map) body);
                    httpMethod.setQueryString(nameValuePairs);
                } else if (body instanceof String) {
                    queryString = (String) body;
                    if (StringUtils.isNotBlank(queryString)) {
                        try {
                            queryString = URIUtil.encodeQuery(queryString, "UTF-8");
                        } catch (URIException e) {
                            throw new RuntimeException(e);
                        }
                        httpMethod.setQueryString(queryString);
                    }
                } else {
                    throw new RuntimeException("Batch element Expected. Got - " + body.getClass());
                }
            } else {
                if (body instanceof Map) { //content-Type: application/x-www-form-urlencoded
                    NameValuePair[] nameValuePairs = MapToNameValuePair((Map) body);
                    ((PostMethod) httpMethod).setRequestBody(nameValuePairs);
                } else if (body instanceof String) {  //content-Type: application/text
                    ((PostMethod) httpMethod).setRequestBody((String) body);
                } else if (body instanceof Part[]) { //content-type: multipart/form-data
                    RequestEntity requestEntity = new MultipartRequestEntity((Part[]) body, httpMethod.getParams());
                    ((PostMethod) httpMethod).setRequestEntity(requestEntity);
                } else {
                    throw new RuntimeException("Batch element Expected. Got - " + body.getClass());
                }
            }

            Object result;
            int statusCode;
            InputStream input;
            try {
                statusCode = client.executeMethod(httpMethod);
                input = httpMethod.getResponseBodyAsStream();
                result = InputStreamToString(input);
            } catch (IOException e) {
                throw new RuntimeException("Error while write from HTTP POST! " + e.getMessage());
            }
            if (abnormal(statusCode, String.valueOf(result))) {
                throw new RuntimeException("调用: " + Tools.replace(url) + " 失败！\n " + httpMethod.getStatusLine() + " \n" + result);
            }
            if (getReturnHeaders()) {
                Map<String, String> headers = new HashMap<>();
                for (Header responseHeader : httpMethod.getResponseHeaders()) {
                    headers.put(responseHeader.getName(), responseHeader.getValue());
                }
                Object o;
                try {
                    o = JSONObject.parseObject((String) result);
                } catch (Exception e) {
                    o = result;
                }
                return (R) MapUtil.mapper("headers", headers, "body", o);
            }
            return (R) result;
        } catch (Exception e) {
            throw e;
        } finally {
            if (httpMethod != null) {
                httpMethod.releaseConnection();
            }
        }
    }

    protected boolean abnormal(int statusCode, String result) {
        // 针对于APP返回状态码做特殊处理
        if ("APP".equals(this.responseType)) {
            try {
                JSONObject.parseObject(result);
                return false;
            } catch (Exception e) {
                return true;
            }
        }
        return statusCode != HttpStatus.SC_OK;
    }

    private String InputStreamToString(InputStream input) {
        if (input == null) return null;
        byte[] data = InputStreamToByteArray(input);
        try {
            return new String(data, enc);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private byte[] InputStreamToByteArray(InputStream in) {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        try {
            int count;
            byte[] data = new byte[BUFFER_SIZE];
            while ((count = in.read(data, 0, BUFFER_SIZE)) != -1) {
                outStream.write(data, 0, count);
            }
            return outStream.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                outStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private NameValuePair[] MapToNameValuePair(Map data) {
        NameValuePair[] result = new NameValuePair[data.size()];
        List<NameValuePair> values = new ArrayList();
        for (Object key : data.keySet()) {
            NameValuePair value = new NameValuePair();
            value.setName(String.valueOf(key));
            value.setValue(String.valueOf(data.get(key)));
            values.add(value);
        }
        return values.toArray(result);
    }

    //Inner class for UTF-8 support
    class UTF8PostMethod extends PostMethod {

        public UTF8PostMethod(String url) {
            super(url);
        }

        @Override
        public String getRequestCharSet() {
            //return super.getRequestCharSet();
            return enc;
        }
    }
}
